home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 25
/
Cream of the Crop 25.iso
/
doom
/
axxwar_1.zip
/
SOURCES
/
CBOTAI.QC
< prev
next >
Wrap
Text File
|
1997-03-05
|
50KB
|
1,718 lines
// AxxWars v0.8
/*
CBOT AI ENGINE
Copyright Cameron Newham, 1996
Motto: "All other bots must die!"
*/
void() cbt_shot1;
void() cbt_nail1;
void() cbt_light1;
void() cbt_rocket1;
void() cbt_blaze1;
void() W_FireShotgun;
void() W_FireSuperShotgun;
void() W_FireGrenade;
void() W_FireRocket;
void() WaterMove;
void() CheckRules;
void() CheckPowerups;
// cn_wpt spawn function
void () cn_wpt =
{
if (!self.speed)
self.speed = 0;
if (!self.height)
self.height = 1;
if (self.angles == '0 0 0')
self.angles = '0 360 0';
if (!self.button0)
self.button0 = 0;
SetMovedir();
if (!self.swim_flag)
self.swim_flag = FALSE;
};
// slide the cbot sideways
void (float slide_amount) cbot_slide =
{
local float ofs;
if (self.lefty)
ofs = 90;
else
ofs = -90;
if (walkmove (self.ideal_yaw + ofs, (random() * slide_amount) + 12))
return;
self.lefty = 1 - self.lefty;
walkmove (self.ideal_yaw - ofs, (random() * slide_amount) + 12);
};
/*-----------------------------------------------------------------
CBOT Fighting Functions
-----------------------------------------------------------------*/
/*
Detect incoming missile - return true if there
is a missile within 270 units (within 0.27 seconds of impact)
Also works for grenades on ground.
*/
float () cbot_incoming_missile =
{
local entity article;
article = findradius(self.origin, 270);
while (article)
{
if ((article.classname == "rocket") ||
(article.classname == "grenade"))
{
if (article.owner != self)
{
return TRUE;
}
}
article = article.chain;
}
return FALSE;
};
/*----------------------------------------------
The player is in view, so decide to move or launch an attack
Returns FALSE if movement should continue
----------------------------------------------*/
float() cbot_check_attack =
{
local vector spot1, spot2;
local entity targ;
local float chance;
local float r;
targ = self.enemy;
// see if any entities are in the way of the shot
spot1 = self.origin + self.view_ofs;
spot2 = targ.origin + targ.view_ofs;
traceline (spot1, spot2, FALSE, self);
if (trace_ent != targ)
return FALSE; // don't have a clear shot
if (trace_inopen && trace_inwater)
return FALSE; // sight line crossed contents
if (time < self.attack_finished)
return FALSE;
r = vlen (spot1 - spot2);
if (r > 1600)
return FALSE;
if (r < 120)
{
chance = 0.97;
}
else if (r < 500)
{
chance = 0.87;
}
else if (r < 1300)
{
chance = 0.67;
}
else
chance = 0;
if (random () < chance)
return TRUE;
return FALSE;
};
/*----------------------------------------------
Determine if we are facing the target +/- 0.5 Deg
in yaw, +/- 1 Deg pitch
----------------------------------------------*/
float() cbot_facing_ideal =
{
local float delta_yaw;
local float delta_pitch;
delta_yaw = anglemod(self.angles_y - self.ideal_yaw);
delta_pitch = fabs(self.ideal_pitch - self.angles_x);
if ((delta_yaw > 0.5 && delta_yaw < 359.5) || (delta_pitch > 1))
return FALSE;
return TRUE;
};
void() cbot_set_ammo =
{
self.items = self.items - ( self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS) );
if (self.weapon == IT_NONE)
{
self.currentammo = 0;
}
else if (self.weapon == IT_SHOTGUN)
{
self.currentammo = self.ammo_shells;
self.items = self.items | IT_SHELLS;
}
else if (self.weapon == IT_SUPER_SHOTGUN)
{
self.currentammo = self.ammo_shells;
self.items = self.items | IT_SHELLS;
}
else if (self.weapon == IT_NAILGUN)
{
self.currentammo = self.ammo_nails;
self.items = self.items | IT_NAILS;
}
// AXXBL START
else if (self.weapon == IT_BLAZEGUN)
{
self.currentammo = self.ammo_nails;
self.items = self.items | IT_NAILS;
}
// AXXBL END
else if (self.weapon == IT_SUPER_NAILGUN)
{
self.currentammo = self.ammo_nails;
self.items = self.items | IT_NAILS;
}
else if (self.weapon == IT_GRENADE_LAUNCHER)
{
self.currentammo = self.ammo_rockets;
self.items = self.items | IT_ROCKETS;
}
else if (self.weapon == IT_ROCKET_LAUNCHER)
{
self.currentammo = self.ammo_rockets;
self.items = self.items | IT_ROCKETS;
}
else if (self.weapon == IT_LIGHTNING)
{
self.currentammo = self.ammo_cells;
self.items = self.items | IT_CELLS;
}
else
{
self.currentammo = 0;
}
};
/*
Determine the best weapon to use against the current enemy.
This incorporates corrections for near R/L use and grenade, etc.
*/
float() cbot_best_weapon_now =
{
local float it;
local entity targ;
local float targ_dist;
local vector targ_pitch;
local vector spot1, spot2;
targ = self.enemy;
spot1 = self.origin; // + self.view_ofs;
spot2 = targ.origin; // + targ.view_ofs; // may not be defnd for cbot! check this
targ_dist = vlen (spot1 - spot2);
it = self.items;
if((self.ammo_cells >= 1) && (it & IT_LIGHTNING) && (targ_dist < 950))
return IT_LIGHTNING;
else if(self.ammo_rockets >= 1 && (it & IT_ROCKET_LAUNCHER) &&
((targ_dist > 150) || (self.health >= 90)))
return IT_ROCKET_LAUNCHER;
else if(self.ammo_rockets >= 1 && (it & IT_GRENADE_LAUNCHER) &&
((targ_dist < 500) || (self.ideal_pitch < 0)) &&
((targ_dist > 120) || (self.health >= 90)))
return IT_GRENADE_LAUNCHER;
else if(self.ammo_nails >= 2 && (it & IT_SUPER_NAILGUN) &&
(targ_dist < 1000))
return IT_SUPER_NAILGUN;
// AXXBL START
if((self.ammo_nails >= 2) && (it & IT_BLAZEGUN) && (targ_dist < 950))
return IT_BLAZEGUN;
// AXXBL END
else if(self.ammo_nails >= 1 && (it & IT_NAILGUN) )
return IT_NAILGUN;
else if(self.ammo_shells >= 2 && (it & IT_SUPER_SHOTGUN) )
return IT_SUPER_SHOTGUN;
else if(self.ammo_shells >= 1 && (it & IT_SHOTGUN) )
return IT_SHOTGUN;
return IT_NONE;
};
/*
Determine best weapon on picking up backpacks/ammo
*/
float() cbot_best_weapon =
{
local float it;
it = self.items;
// AXXBL START else
if(self.ammo_nails >= 2 && (it & IT_BLAZEGUN) )
return IT_BLAZEGUN;
// AXXBL END
if(self.ammo_cells >= 1 && (it & IT_LIGHTNING) )
return IT_LIGHTNING;
else if(self.ammo_rockets >= 1 && (it & IT_ROCKET_LAUNCHER) )
return IT_ROCKET_LAUNCHER;
else if(self.ammo_rockets >= 1 && (it & IT_GRENADE_LAUNCHER) )
return IT_GRENADE_LAUNCHER;
else if(self.ammo_nails >= 2 && (it & IT_SUPER_NAILGUN) )
return IT_SUPER_NAILGUN;
else if(self.ammo_shells >= 2 && (it & IT_SUPER_SHOTGUN) )
return IT_SUPER_SHOTGUN;
else if(self.ammo_nails >= 1 && (it & IT_NAILGUN) )
return IT_NAILGUN;
else if(self.ammo_shells >= 1 && (it & IT_SHOTGUN) )
return IT_SHOTGUN;
return IT_NONE;
};
/*---------------------------------------------
check if we have ammo or not and change weapons
if we are out.
---------------------------------------------*/
float() cbot_check_no_ammo =
{
if (self.currentammo > 0)
return TRUE;
if (self.weapon == IT_NONE)
return TRUE;
self.weapon = cbot_best_weapon_now ();
cbot_set_ammo ();
// drop the weapon down
return FALSE;
};
/*---------------------------------------------
Called from combat.qc for attacks on us
Determines what action to take:
ATTACK - attack outright
OTFATTACK - continue search but fire at enemy
WITHDRAW - try and hide
If inflictor != world then we have been hit
by a missile - we can use this info to determine
enemy strengths.
---------------------------------------------*/
float (entity e_target, entity e_inflictor) cbot_determine_mode =
{
if ((self.botmode == CAMPER) ||
(self.items & IT_INVISIBILITY) ||
(self.items & IT_INVULNERABILITY) ||
(self.items & IT_QUAD))
return ATTACK; // de rigor for a camper or specials
if (self.weapon == IT_NONE)
return WITHDRAW; // de rigor for empty handed combatent
if ((self.armorvalue > 95) ||
(self.health <= 7))
return ATTACK; // attack with whatever we have if great armour
// or we are nearly dead
if ((self.armorvalue > 50) &&
((self.pi9 == 1) ||
(self.pi8 == 1) ||
(self.pi7 == 1) ||
(self.pi6 == 1) ||
(self.pi5 == 1) ||
(self.pi4 == 1)))
return ATTACK; // if good armour and weapons > sg then do it
if ((self.armorvalue > 20) &&
((self.pi8 == 1) ||
(self.pi7 == 1) ||
(self.pi6 == 1) ||
(self.pi5 == 1) ||
(self.pi4 == 1)))
return ATTACK; // if poor armour, good weapons
// then do it
if ((self.botmode == SEARCH) ||
(self.botmode == OTFATTACK))
{
// no armour so be careful
if (e_inflictor)
{
if (e_inflictor.classname != "rocket")
return ATTACK; // no armour and attack wasn't a rocket and
// WE were attacked
}
else
if ((self.health > (e_target.health * 1.2)) ||
(random() > 0.95))
return ATTACK; // we are attacking someone - do it if our health
// is 1.2 x better than theirs OR random choice
}
if ((self.botmode == SEARCH) ||
(self.botmode == OTFATTACK))
return OTFATTACK; // if we are searching and have any weapon
// then use it while searching
return WITHDRAW; // we are in trouble so hide!
};
/*---------------------------------------------
Fire the current weapon
---------------------------------------------*/
void() cbot_fire_weapon =
{
if (!cbot_check_no_ammo ())
return;
// if (cbot_friendly == 1) // Debug: Don't shoot at all.
// return;
self.v_angle = self.angles;
self.show_hostile = time + 1; // wake monsters up
if (self.weapon == IT_NONE)
return; // should never happen
else if (self.weapon == IT_SHOTGUN)
{
self.v_angle_x = -1 * self.v_angle_x;
cbt_shot1 ();
makevectors (self.v_angle);
W_FireShotgun ();
self.attack_finished = time + 0.5;
}
else if (self.weapon == IT_SUPER_SHOTGUN)
{
self.v_angle_x = -1 * self.v_angle_x;
cbt_shot1 ();
makevectors (self.v_angle);
W_FireSuperShotgun ();
self.attack_finished = time + 0.7;
}
else if (self.weapon == IT_NAILGUN)
{
self.blasttime = time + 2.3 + 2.1 * random();
self.think = cbt_nail1; //nails & lightning as thinks! others as subs
self.nextthink = time + 0.1;
}
// AXXBL START
else if (self.weapon == IT_BLAZEGUN)
{
self.v_angle_x = self.v_angle_x * -1;
cbt_blaze1;
self.nextthink = time + 0.1;
}
// AXXBL END
else if (self.weapon == IT_SUPER_NAILGUN)
{
self.blasttime = time + 1.8 + 2 * random();
self.think = cbt_nail1;
self.nextthink = time + 0.1;
}
else if (self.weapon == IT_GRENADE_LAUNCHER)
{
self.v_angle_x = self.v_angle_x * -1;
cbt_rocket1();
W_FireGrenade();
self.attack_finished = time + 0.6;
}
else if (self.weapon == IT_ROCKET_LAUNCHER)
{
self.v_angle_x = self.v_angle_x * -1;
cbt_rocket1();
W_FireRocket();
self.attack_finished = time + 0.8;
}
else if (self.weapon == IT_LIGHTNING)
{
self.blasttime = time + 1.5 + 2 * random();
self.think = cbt_light1;
self.nextthink = time + 0.1;
// self.attack_finished = time + 0.1;
sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM);
}
};
/*----------------------------------------------------
Turns to face the target - includes a correction
for the enemy's velocity
----------------------------------------------------*/
void() cbot_track_target =
{
local vector los;
local vector los_angles;
local float o_range;
local float missile_factor;
local vector spotty_squid; // adds a tiny random for stationary targets
local string st;
o_range = vlen(self.enemy.origin - self.origin);
if ((self.weapon == IT_ROCKET_LAUNCHER) ||
(self.weapon == IT_NAILGUN) ||
(self.weapon == IT_SUPER_NAILGUN)
|| (self.weapon == IT_BLAZEGUN) // AXXBL
)
missile_factor = o_range / (1900 + random() * 700); // add scatter
else if (self.weapon == IT_GRENADE_LAUNCHER)
missile_factor = o_range / (1000 + random() * 50); // add scatter
else
missile_factor = 0; //other weapons are instantaneous
// apply a small randomiser of the distance is large
if (o_range > 300)
spotty_squid = ('9 9 8') * (random() - 0.5);
else
spotty_squid = '0 0 0';
// Face cbot towards the specified angles
los = self.enemy.origin - self.origin + self.enemy.velocity*missile_factor
+ spotty_squid;
los_angles = vectoangles (los);
self.ideal_yaw = los_angles_y;
self.ideal_pitch = los_angles_x;
if (self.ideal_pitch > 180)
self.ideal_pitch = self.ideal_pitch - 360; //stupid id mistake
ChangeYaw();
ChangePitch();
};
/*---------------------------------------------
Launch an attack at the self.enemy
---------------------------------------------*/
void() cbot_attack =
{
if (cbot_incoming_missile())
cbot_slide(12);
if (visible (self.enemy))
{
cbot_track_target();
self.sighted_time = time + 1 + 2 * random(); //wait random 2-3 seconds
if (cbot_check_attack() ||
(self.botmode == OTFATTACK))
{
// only launch an attack if we feel like it
// or if we are attacking on-the-fly (once only)
// and pointing in the right direction.
if (cbot_facing_ideal())
// if (infront(self.enemy))
if (time > self.attack_finished)
{
self.weapon = cbot_best_weapon_now (); // need to do this to set best weapon for job
cbot_fire_weapon();
}
}
else
{
//bprint ("readjust position instead of fire\n");
cbot_track_target();
if (self.botmode != JUMP)
{
// only do this if not in mid-air
cbot_slide(14);
}
}
}
else
{
// Enemy is not visible (grenade attack, hiding or delayed die)
// so move
if ((self.sighted_time < time) ||
(self.enemy.health <= 0))
{
// we waited for a time but the enemy has gone. go back
// to normal duties.
self.botmode = SEARCH;
self.enemy = world;
self.goal_status = 0;
}
}
if (cbot_incoming_missile())
cbot_slide(8);
};
void() cbot_attack_initial =
{
cbot_attack();
};
/*-----------------------------------------------------------------
Move and General Functions
-----------------------------------------------------------------*/
void() cbot_jump =
{
local vector jump_vec;
local float vel;
// set XY even if not on ground, so the jump will clear lips
vel = 240 + random() * 25;
makevectors (self.angles); // calculate pointing vectors
self.velocity_x = v_forward_x * vel;
self.velocity_y = v_forward_y * vel;
if ( (self.flags & FL_ONGROUND) )
{
self.flags = self.flags - FL_ONGROUND;
self.velocity_z = vel;
self.botmode = JUMP;
}
// allow some time for the jumping and the next check
// as we may be jumping into a teleporter and we want to
// find the next waypoint on the other side.
self.nextthink = time + 0.3;
sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
};
void() cbot_watchdog =
{
local float dist;
local vector test_vectors;
local vector top;
// check to see if the bot has moved or is stuck
dist = vlen (self.origin - self.old_posn);
if (dist < 25)
{
// we are probably stuck
makevectors (self.angles); // calculate pointing vectors
test_vectors = self.origin + (self.maxs_x + 16) * v_forward;
top = self.origin;
test_vectors_z = test_vectors_z + self.maxs_z;
top_z = top_z + self.maxs_z;
traceline (top, test_vectors, FALSE, self);
if (trace_fraction < 1.0)
{
// we have hit a wall
self.goalentity = world; //nullify any previous goal
self.goal_status = 0;
self.think = self.th_stand; // go and reconfigure
}
else
{
// could be a horizontal grill or a gap
top = self.origin + ((self.maxs_x + 16) * trace_fraction) * v_forward;
test_vectors = top;
test_vectors_z = test_vectors_z - 256; // drop a plumb well down
traceline (top, test_vectors, TRUE, self);
if ((256 * trace_fraction) < (self.size_z - 8))
{
// must be a grill or a low obstacle
self.goalentity = world; //nullify any previous goal
self.goal_status = 0;
self.think = self.th_stand; // go and reconfigure
} else
{
cbot_jump();
}
/*
// clever code that doesn't seem to work...
// probably needs tweaking to make it work
if ((256 * trace_fraction) > (self.size_z + 4))
{
// must be next to a surface discontinuity, so jump
bprint ("hit abyss\n");
cbot_jump();
} else
{
// may be over a surface discontinuity
test_vectors = self.origin;
test_vectors_z = test_vectors_z - 256; // drop plumb line
traceline (self.origin, test_vectors, TRUE, self);
if ((256 * trace_fraction) > (self.size_z + 4))
{
// we are over a surface discontinuity (gap)
bprint ("over gap\n");
cbot_jump();
}
else
{
// we are stuck (reasons unknown)
bprint ("C-Bot ");
bprint (self.targetname);
bprint (" is stuck\n");
}
}
*/
}
}
self.old_posn = self.origin; //update the old position
};
float (float itype) cbot_got_item =
{
if (itype == 23)
return self.pi1;
if (itype == 22)
return self.pi2;
if (itype == 21)
return self.pi3;
if (itype == 16)
return self.pi4;
if (itype == 15)
return self.pi5;
if (itype == 14)
return self.pi6;
if (itype == 13)
return self.pi7;
if (itype == 12)
return self.pi8;
if (itype == 11)
return self.pi9;
};
void() cbot_prioritise =
{
local float it;
it = self.items;
// this code here is a hack so that weapons gathered from
// a backpack will show up on list - here just to sync everything
// (perhaps it should be in backpack section of items.qc - oh well)
// Addendum: this code also deals with lack of ammo - if the weapon
// has run out then it is considered useless (not found)
if(self.ammo_cells >= 1 && (it & IT_LIGHTNING) )
self.pi5 = 1;
else
self.pi5 = 0;
if(self.ammo_rockets >= 1 && (it & IT_ROCKET_LAUNCHER) )
self.pi4 = 1;
else
self.pi4 = 0;
if(self.ammo_rockets >= 1 && (it & IT_GRENADE_LAUNCHER) )
self.pi6 = 1;
else
self.pi6 = 0;
if(self.ammo_nails >= 2 && (it & IT_SUPER_NAILGUN) )
self.pi7 = 1;
else
self.pi7 = 0;
if(self.ammo_shells >= 2 && (it & IT_SUPER_SHOTGUN) )
self.pi9 = 1;
else
self.pi9 = 0;
if(self.ammo_nails >= 1 && (it & IT_NAILGUN) )
self.pi8 = 1;
else
self.pi8 = 0;
// armour damage changes our priorities!
if (self.armorvalue < 150)
self.pi1 = 0;
if (self.armorvalue < 100)
self.pi2 = 0;
if (self.armorvalue < 50)
self.pi3 = 0;
/* // get low on rockets - so re-prioritise to important
if (self.ammo_rockets < 2)
{
self.pi4 = 0;
self.pi6 = 0;
}
if (self.ammo_nails < 4)
{
self.pi7 = 0;
self.pi8 = 0;
}
*/
};
/*---------------------------------------------
Find nearest enemy
---------------------------------------------*/
entity () cbot_find_nearest_enemy =
{
local entity article, best_article;
local float o_range;
local float best_dist;
local vector los, los_angles;
best_article = world;
best_dist = 9999;
if (cbot_friendly != 1)
{
// find closest player
article = find(world, classname, "player");
while (article != world)
{
o_range = vlen(article.origin - self.origin);
traceline (self.origin, article.origin, TRUE, self);
if ((trace_fraction == 1.0) &&
(o_range <= best_dist) &&
(article.health > 0) &&
(article != self))
{
los = article.origin - self.origin; // determine if pitch too great
los_angles = vectoangles (los);
if (los_angles_x > 180)
los_angles_x = los_angles_x - 360; //vectoangles returns 0 to 360
// NOT negative values
if (fabs(los_angles_x) < 54)
{
best_article = article;
best_dist = o_range;
}
}
//next
article = find(article, classname, "player");
} //end while
}
// find closest cbot
article = find(world, classname, "cbot");
while (article != world)
{
o_range = vlen(article.origin - self.origin);
traceline (self.origin, article.origin, TRUE, self);
if ((trace_fraction == 1.0) &&
(o_range <= best_dist) &&
(article.health > 0) &&
(article != self))
{
los = article.origin - self.origin; // determine if pitch too great
los_angles = vectoangles (los);
if ((los_angles_x < 54) || (los_angles_x > 306))
{
best_article = article;
best_dist = o_range;
}
}
//next
article = find(article, classname, "cbot");
} //end while
return best_article;
};
/*-------------------------------------------
Get the nearest visible prioritised waypoint.
findp: 0 = any paths 1 = items 2 = general path,
ignore: 100 = none ignored # = ignore item type
endp: 0 = ignore endpoints 1 = find endpoints
Returns world if none available.
-------------------------------------------*/
entity( float findp, float ignore, float endp ) cbot_get_waypoint =
{
local float o_range;
local entity article;
local float best_dist;
local entity best_article;
local float best_items;
local vector los, los_angles;
best_dist = 9999.0; // set our best range to a large value;
best_article = world;
best_items = 0;
// find a waypoint and go to it
article = find(world, classname, "cn_wpt");
while (article != world)
{
// find best waypoint
if (((findp == 1) && (article.items > best_items)) ||
((findp == 2) && (article.items == 0)) ||
(findp == 0))
{
o_range = vlen(article.origin - self.origin);
traceline (self.origin, article.origin, TRUE, self);
if ((trace_fraction == 1.0) &&
((o_range <= best_dist) || // less range
(article.items > best_items)) &&
(!cbot_got_item (article.items)) &&
(article != self.goalentity) &&
((ignore == 100) || (article.items != ignore)) &&
((article.target != "-") || endp))
{
// ignore anything that's blocked or bigger than best range
// (unless its higher priority) or if we have it
// or if we just came from there (end waypoints) or
// any end waypoint (which are useless, unless told to).
// now ignore anything below our field of view
los = article.origin - self.origin; // determine if pitch too great
los_angles = vectoangles (los);
if (los_angles_x > 180)
los_angles_x = los_angles_x - 360; //vectoangles returns 0 to 360
// NOT negative values
if (fabs(los_angles_x) < 54)
{
best_article = article;
best_dist = o_range;
best_items = article.items;
}
}
}
//next
article = find(article, classname, "cn_wpt");
} //end while
return (best_article);
};
/*---------------------------------------------
creates a sub-goal. used to target objects
like packs, weapons, armour and health, etc.
---------------------------------------------*/
void(entity article) cbot_create_subgoal =
{
self.origin_save = spawn(); // save origin for backtracking
self.origin_save.think = SUB_Remove;
self.origin_save.nextthink = time + 120; //remove after 2 mins
// save the primary goal target (where we were going to
// when interrupted to do the sub-goal
if (self.goalentity != world)
self.oldgoalname = self.goalentity.targetname;
else
self.oldgoalname = "none";
setorigin (self.origin_save, self.origin);
self.goal_status = 3; // on a sub-goal now
/* we need to spawn a sub-goal for the article because when
it gets touched it may disappear (cf: backpack) and the
origin will become invalid */
self.goalentity = spawn(); // spawn a sub-goal
self.goalentity.think = SUB_Remove;
self.goalentity.nextthink = time + 140;
setorigin (self.goalentity, article.origin);
};
/*---------------------------------------------------
Checks to see if the ammo is at maximum already.
Broken out into separate procedure for readability.
---------------------------------------------------*/
float (entity article) cbot_check_max_ammo =
{
if ((article.classname == "item_cells") &&
(self.ammo_cells == 100))
return FALSE;
if ((article.classname == "item_rockets") &&
(self.ammo_rockets == 100))
return FALSE;
if ((article.classname == "item_shells") &&
(self.ammo_shells == 100))
return FALSE;
if ((article.classname == "item_spikes") &&
(self.ammo_nails == 200))
return FALSE;
return TRUE;
};
/*---------------------------------------------------
Find on-the-fly items like health, ammo and specials.
These are prioritised automagically in this routine
to get what the bot really needs at the time. Order is:
health (<50%)
specials
ammo
Returns "world" if none.
---------------------------------------------------*/
entity () find_otf_items =
{
local float o_range;
local entity article;
local float best_dist;
local entity best_article;
// find any nearby item
article = findradius (self.origin, 200.0);
best_dist = 9999.0; // set our best range to a large value;
best_article = world;
while (article)
{
o_range = vlen(article.origin - self.origin);
// trace a line - raise target slightly for visibility on stairs, etc
traceline (self.origin, article.origin + '0 0 10', TRUE, self);
if (trace_fraction == 1.0)
{
// ignore anything that's blocked or not health/ammo/special
if ((article.classname == "item_cells") ||
(article.classname == "item_rockets") ||
(article.classname == "item_shells") ||
(article.classname == "item_spikes") ||
(article.classname == "item_health") ||
(article.classname == "item_artifact_super_damage") ||
(article.classname == "item_artifact_invunerability") ||
(article.classname == "item_artifact_invisibility"))
{
/* if (best_article != world)
{
*/
if ((self.health < 50) &&
(article.classname == "item_health"))
{
best_article = article;
best_dist = o_range;
}
else
if ((self.health < 50) &&
(best_article.classname != "item_health"))
{
// need health, haven't found any yet
// so this will do (if its closer than best)
// unless we have max ammo (if its ammo)
if ((o_range < best_dist) &&
(cbot_check_max_ammo(article)))
{
best_article = article;
best_dist = o_range;
}
} //ignore article if we have and need health
else
if (article.classname == "item_health")
{
if (self.health < self.max_health)
{
best_article = article;
best_dist = o_range;
}
}
else
{
if ((article.classname == "item_artifact_super_damage") ||
(article.classname == "item_artifact_invunerability") ||
(article.classname == "item_artifact_invisibility"))
{
//health is ok - this is a special (don't care about
//distance either)
best_article = article;
best_dist = o_range;
}
else
if (((best_article.classname != "item_artifact_super_damage") &&
(best_article.classname != "item_artifact_invunerability") &&
(best_article.classname != "item_artifact_invisibility")) &&
(o_range < best_dist) &&
(cbot_check_max_ammo(article)))
{
// no health prob., it's not a special. we ignore
// so it is ammo - grab it.
best_article = article;
best_dist = o_range;
}
}
/* }
else
{
// currently world
best_article = article;
best_dist = o_range;
}
*/
}
}
article = article.chain; // go to next in list
} // end while
return (best_article);
};
void() cbot_ai_stand =
{
local float o_range;
local entity article;
local float best_dist;
local entity best_article;
local vector camp_origin;
local float cbot_mode;
CheckPowerups();
CheckRules ();
WaterMove ();
/* MARK for deletion - need to be able to fire while in air.
now handled by having "JUMP" mode
if (self.botmode == JUMP)
{
// don't do a think while in the air and not projected
// to do any swimming. Needed so we don't miscalculate
// waypoint searches while in the air and heading into
// a teleport.
return;
}
*/
//if in jump mode and on ground then go back to searching
if ((self.botmode == JUMP) &&
(self.flags & FL_ONGROUND))
self.botmode = SEARCH;
/* MARK for deletion
//Deal with Quad Damage
if (self.items & IT_QUAD)
{
if (self.super_damage_finished < time)
{ // just stopped
self.items = self.items - IT_QUAD;
self.super_damage_finished = 0;
self.super_time = 0;
}
if (self.super_damage_finished > time)
self.effects = self.effects | EF_DIMLIGHT;
else
self.effects = self.effects - (self.effects & EF_DIMLIGHT);
}
*/
if (cbot_incoming_missile())
cbot_slide(8);
// Check to see if we want to attack anyone nearby
// cbot_mode = cbot_determine_mode (world, world);
// if (cbot_mode == ATTACK)
// {
article = cbot_find_nearest_enemy();
if (article)
{
cbot_mode = cbot_determine_mode (article, world);
// only change if it is specifically ATTACK mode - withdraw
// or OTFATTACK can't be delt with here
if (cbot_mode == ATTACK)
{
self.botmode = ATTACK;
self.enemy = article;
}
}
// }
if (self.botmode == WITHDRAW)
{
self.enemy = world;
self.botmode = SEARCH; // ******* this is for now. we need some logic
// behind a withdrawl
self.goal_status = 0;
}
if (self.botmode == SEARCH)
{
if (self.goal_status == 0)
{
// see if we have the capability to camp!
if ((self.health >= 70) &&
((self.pi4) || (self.pi5) || (self.pi6) || (self.pi7)) &&
(self.armorvalue >= 40) &&
(random() < 0.60) &&
(self.goalentity.button0))
{
// must be > 40% armour, > 70% health, r/l, g/l, lg or sng
// and 60% chance we decide to
self.camp_time = time + 120 + 90 * random();
self.goal_status = 1; // on a waypoint goal now
self.botmode = CAMPER;
camp_origin = self.goalentity.view_ofs;
self.think = self.th_run;
self.nextthink = time + 0.1;
self.goalentity = spawn(); // spawn a goal at the camp posn
self.goalentity.think = SUB_Remove;
self.goalentity.nextthink = time + 140;
setorigin (self.goalentity, camp_origin);
}
else
{
// find any nearby weapon or backpack and grab it
article = findradius (self.origin, 212.0);
best_dist = 9999.0; // set our best range to a large value;
best_article = world;
while (article)
{
o_range = vlen(article.origin - self.origin);
// trace a line - raise target slightly for visibility on stairs, etc
traceline (self.origin, article.origin + '0 0 10', TRUE, self);
if (trace_fraction == 1.0)
{
// ignore anything that's blocked or not a weapon/backpack/armour
if ((o_range <= best_dist) &&
((article.classname == "weapon_supershotgun") ||
(article.classname == "weapon_nailgun") ||
(article.classname == "weapon_supernailgun") ||
(article.classname == "weapon_grenadelauncher") ||
(article.classname == "weapon_rocketlauncher") ||
(article.classname == "weapon_lightning") ||
(article.classname == "backpack") ||
(article.classname == "item_armor1") ||
(article.classname == "item_armor2") ||
(article.classname == "item_armorInv")))
{
if (best_article != world)
{
if (best_article.classname == "backpack")
{
//this backpack or weapon is closer, so grab it!
best_article = article;
best_dist = o_range;
}
else
{
// be more choosie - we already have a best weapon
// so only another weapon will do
if (article.classname != "backpack")
{
//it's a weapon so get it
best_article = article;
best_dist = o_range;
}
}
}
else
{
// we have no best, so make this the best so far
best_article = article;
best_dist = o_range;
}
}
}
article = article.chain; // go to next in list
} // end while
// if we find a sub-goal, go to it.
if (best_article != world)
{
// we have a local target, so make this a sub-goal
cbot_create_subgoal(best_article);
self.think = self.th_run;
self.nextthink = time + 0.1;
}
else
{
// try to get a new waypoint - general or item
best_article = cbot_get_waypoint(0,100,0);
if (best_article != world)
{
// got a waypoint to go to, so do it!
self.goalentity = best_article; // article is the goal
self.goal_status = 1; // on a waypoint goal now
self.think = self.th_run;
self.nextthink = time + 0.1;
}
else
{
// oops! can't find a waypoint!
// try a search strategy
//bprint ("stuck - try search\n");
best_article = cbot_get_waypoint(0,100,1);
if (best_article != world)
{
// find the preceeding waypoint to closest waypoint
best_article = find (world, target, best_article.targetname);
if (best_article != world)
{
self.goalentity = best_article;
self.goal_status = 1;
self.think = self.th_run;
self.nextthink = time + 0.1;
return;
}
// else
//bprint ("no previous waypoint\n");
}
//bprint ("no waypoint found - idling\n");
self.angles_y = 360 * random();
cbot_jump();
}
}
}
} // end if self.goal_status = 0
else
if (self.goal_status = 2)
{
//off-route on a sub-goal so backtrack to original posn
self.goal_status = 4; //on-route backtracking
self.goalentity = self.origin_save;
self.think = self.th_run;
self.nextthink = time + 0.1;
}
self.watchdog_time = time + 1.2; // try again in 1.2 seconds
}
else if (self.botmode == CAMPER)
{
// we must have reached the camp wpt in th_stand - so now
// we just wait it out!
// check to see if we want to attack anyone nearby
// if (cbot_determine_mode (world, world) == ATTACK)
// {
self.enemy = cbot_find_nearest_enemy();
if (self.enemy != world)
{
self.botmode = cbot_determine_mode (self.enemy, world);
return;
}
// }
if (self.camp_time < time)
self.botmode = SEARCH;
// Face cbot towards the specified angle
self.ideal_yaw = self.goalentity.angles_y;
ChangeYaw();
}
else if (self.botmode == ATTACK)
{
// attack until the enemy is dead
cbot_attack_initial();
if (self.enemy.health <= 0)
{
//bprint("Enemy is dead!\n");
//enemy is dead, so continue
self.enemy = world;
self.botmode = SEARCH;
self.goal_status = 0;
self.angles_x = 0; // make sure we reset the pitch on loss of target!
// should really be ChangePitch here
}
if (self.weapon == IT_NONE)
{
//bprint ("out of ammo\n");
// must have run out of ammo
self.enemy = world;
self.botmode = WITHDRAW;
self.goal_status = 0;
self.angles_x = 0; // make sure we reset the pitch on loss of target!
// should really be ChangePitch here
}
}
else if (self.botmode == OTFATTACK)
{
// only make one attack and then continue search
cbot_attack_initial();
self.enemy = world;
self.botmode = SEARCH;
}
};
void(float dist) cbot_ai_walk =
{
local entity trywpt;
local float o_range;
local float h_range;
local vector h1, h2;
local float cont;
local vector norm_vel;
local entity other_item;
local float cbot_mode;
local entity article;
local string sf;
if (cbot_incoming_missile())
cbot_slide(8);
CheckPowerups();
if (self.botmode == SEARCH)
{
// cont = pointcontents(self.origin);
if (self.goalentity.swim_flag)
{
// if we hit a waypoint that says swim, start testing
// for water (probably jumping into it, in which case we
// don't want to change our velocity until we are in the water)
if ((self.watertype == CONTENT_WATER) ||
(self.watertype == CONTENT_SLIME))
{
// tread water if we are swimming *in* water/slime
self.velocity_z = self.goalentity.origin_z - self.origin_z;
if (self.velocity_z > 0)
self.velocity_z = self.velocity_z * 3.3; //give "up" some kick
self.velocity_y = self.goalentity.origin_y - self.origin_y;
self.velocity_x = self.goalentity.origin_x - self.origin_x;
norm_vel = normalize (self.velocity) * 70;
norm_vel_z = 0;
self.velocity = self.velocity + norm_vel;
}
}
CheckRules ();
WaterMove ();
/* if (cont == CONTENT_LAVA)
{
// do damage from lava
T_Damage (self, world, world, 100);
}
*/
if (self.goal_status == 1)
{
if (self.goalentity.items == 0)
{
// if on a general path, check for other prioritised paths
// first - take them if they are important
trywpt = cbot_get_waypoint(1,0,0);
if (trywpt != world)
{
//bprint ("got prioritised goal\n");
// got an item waypoint to go to, so do it!
self.goalentity = trywpt; // trywpt is the goal
self.goal_status = 1; // still on a waypoint goal
self.think = self.th_run;
self.nextthink = time + 0.1;
return; // exit from current course
}
}
else
{
// we are on a non-general waypoint, but see what else
// there is that is *better* than what we are going for.
trywpt = cbot_get_waypoint(1,0,0);
if (trywpt != world)
if (trywpt.items > self.goalentity.items)
{
//bprint ("swapping goals\n");
// trywpt is a better item so get it
self.goalentity = trywpt; // trywpt is the goal
self.goal_status = 1; // still on a waypoint goal
self.think = self.th_run;
self.nextthink = time + 0.1;
return; // exit from current course
}
}
// Now check for other items of interest (health, special, ammo)
// if the other_item_time watch timer has gone off
if (self.other_item_time < time)
{
other_item = find_otf_items(); //look for other items (health etc)
if (other_item != world)
{
cbot_create_subgoal(other_item); // we found an otf-item so get it
self.other_item_time = time + 0.3; // try again in 0.3 seconds
self.think = self.th_run;
self.nextthink = time + 0.1;
return;
}
}
// on-route to target
o_range = vlen (self.origin - self.goalentity.origin);
h1 = self.origin; h1_z = 0;
h2 = self.goalentity.origin; h2_z = 0;
h_range = vlen(h1 - h2);
if (o_range < 70) // check full range
if (h_range < 20) // check horizontal range
{
if (self.goalentity.jump_flag)
{
self.goalentity.jump_flag = self.goalentity.jump_flag - 1;
// we have to activate a button by jumping
if ( (self.flags & FL_ONGROUND) )
{
self.botmode = JUMP;
self.flags = self.flags - FL_ONGROUND;
self.velocity_z = 50;
}
return;
}
if (self.goalentity.speed > 0)
{
// jump if speed is greater than 0 on waypoint
// set XY even if not on ground, so the jump will clear lips
self.velocity_x = self.goalentity.movedir_x * self.goalentity.speed;
self.velocity_y = self.goalentity.movedir_y * self.goalentity.speed;
if ( (self.flags & FL_ONGROUND) )
{
self.flags = self.flags - FL_ONGROUND;
self.velocity_z = self.goalentity.height;
}
self.nextthink = time + 0.2; //allow some time
sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
}
// got to the target so stop if appropriate.
if ((self.goal_status == 1) &&
(self.goalentity.target != "-"))
{
// still on waypoint route - find next waypoint.
// first, see if we have a choice - randomize it
if (self.goalentity.noise4)
if (random () > 0.4)
self.goalentity = find (world, targetname, self.goalentity.noise4);
else
self.goalentity = find (world, targetname, self.goalentity.target);
else
self.goalentity = find (world, targetname, self.goalentity.target);
}
else
{
// end of waypoint route
self.goal_status = 0;
self.think = self.th_stand;
}
}
}
else
if ((self.goal_status == 3) ||
(self.goal_status == 4))
{
// moving via sub-routes
if (self.goal_status == 3)
{
o_range = vlen (self.origin - self.goalentity.origin);
h1 = self.origin; h1_z = 0;
h2 = self.goalentity.origin; h2_z = 0;
h_range = vlen(h1 - h2);
if (o_range < 70) // check full range
if (h_range < 20) // check horizontal range
{
self.goal_status = 2;
self.think = self.th_stand;
remove (self.goalentity); // remove the target position
return;
}
}
else
if (self.goal_status == 4)
{
o_range = vlen (self.origin - self.origin_save.origin);
h1 = self.origin; h1_z = 0;
h2 = self.origin_save.origin; h2_z = 0;
h_range = vlen(h1 - h2);
if (o_range < 70) // check full range
if (h_range < 20) // check horizontal range
{
// finished on a backtrack route
if (self.oldgoalname != "none")
{
// find our original goal target (before we got interrupted
// to do the sub-goal)
//bprint ("looking for original target ");
//bprint (self.oldgoalname);
//bprint("\n");
self.goalentity = find (world, targetname, self.oldgoalname);
self.goal_status = 1;
}
else
{
// no original target so stop and think
self.goal_status = 0;
self.think = self.th_stand;
}
remove (self.origin_save); //remove the saved position
self.nextthink = time + 0.1;
return;
}
}
}
}
else if (self.botmode == CAMPER)
{
// move towards the camp goal
// no need to check for waypoint types as once we get
// there, th_stand handles everything (and clears CAMPER mode).
o_range = vlen (self.origin - self.goalentity.origin);
h1 = self.origin; h1_z = 0;
h2 = self.goalentity.origin; h2_z = 0;
h_range = vlen(h1 - h2);
if (o_range < 70) // check full range
if (h_range < 20) // check horizontal range
{
self.goal_status = 0; // finished - this can be anything
self.think = self.th_stand;
remove (self.goalentity); // remove the target position
return;
}
}
else if ((self.botmode == ATTACK) ||
(self.botmode == OTFATTACK) ||
(self.botmode == WITHDRAW) ||
(self.botmode == JUMP))
{
// we have to swap modes, so stop running and start thinking
self.think = self.th_stand;
self.nextthink = time + 0.1;
}
// *** General stuff that we have to do every time we move ***
//Turn to look at the target
self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
ChangeYaw();
movetogoal (dist);
if (self.watchdog_time < time)
{
cbot_watchdog();
self.watchdog_time = time + 0.2; // try again in 0.2 seconds
// also check to see if we want to attack anyone nearby
// cbot_mode = cbot_determine_mode (world, world);
// if (cbot_mode == ATTACK)
// {
article = cbot_find_nearest_enemy();
if (article)
{
cbot_mode = cbot_determine_mode (article, world);
// only change if it is specifically ATTACK mode - withdraw
// or OTFATTACK can't be delt with here
if (cbot_mode == ATTACK)
{
self.botmode = ATTACK;
self.enemy = article;
}
}
}
if (self.prioritise_time < time)
{
cbot_prioritise();
self.prioritise_time = time + 1; // do this slowly as we are not
// in attack/defence mode
}
// only set a new time if it hasn't been done already (eg: in jump)
if (self.nextthink < time)
self.nextthink = time + 0.1; // add time
};
void(float dist) cbot_ai_run =
{
cbot_ai_walk(dist);
};